package net.aiion.weave.impl.bytecode;

import java.util.Collection;

import net.aiion.weave.asm.Label;
import net.aiion.weave.asm.Opcodes;
import net.aiion.weave.asm.Type;
import net.aiion.weave.spi.bytecode.Bytecode;
import net.aiion.weave.spi.bytecode.BytecodeEventFactory;
import net.aiion.weave.spi.bytecode.BytecodeException;
import net.aiion.weave.spi.bytecode.BytecodeGenerator;
import net.aiion.weave.spi.bytecode.ResumptionCondition;
import net.aiion.weave.spi.bytecode.events.BytecodeEvent;

public class BytecodeGeneratorImpl extends AbstractBytecodeSource implements BytecodeGenerator
{
    private final BytecodeEventFactory bytecodeEventFactory;

    public BytecodeGeneratorImpl() {
        bytecodeEventFactory = Bytecode.getEventFactory();
    }

    @Override
    public void startClass(final int aModifiers, final String aName, final String aSuperName,
        final String... aInterfaceNames)
    {
        startClass(aModifiers, aName, null, aSuperName, aInterfaceNames);
    }
    
    @Override
    public void startClass(final int aModifiers, final String aName, final Class<?> aSuperClass, final Class<?>... aInterfaces) {
    	startClass(aModifiers, aName, Type.getInternalName(aSuperClass), getInternalNames(aInterfaces));
    }

    @Override
    public void startClass(final int aModifiers, final String aName, final String aSignature, final String aSuperName,
        final String... aInterfaceNames)
    {
        dispatchBytecodeEvent(bytecodeEventFactory.createClassStart(Opcodes.V1_7, aModifiers, aName, aSignature,
            aSuperName, aInterfaceNames));
    }
    
    @Override
    public void startClass(final int aModifiers, final String aName, final String aSignature, final Class<?> aSuperClass, final Class<?>... aInterfaces) {
    	startClass(aModifiers, aName, aSignature, Type.getInternalName(aSuperClass), getInternalNames(aInterfaces));
    }
    
    private String[] getInternalNames(final Class<?>[] aClasses) {
    	final String[] internalNames = new String[aClasses.length];
    	for (int i = 0; i < aClasses.length; i ++) {
    		internalNames[i] = Type.getInternalName(aClasses[i]);
    	}
    	return internalNames;
    }

    @Override
    public void referenceInnerClass(final String aInnerName, final String aOuterName, final String aInnerSimpleName,
        final int aModifiers)
    {
        dispatchBytecodeEvent(bytecodeEventFactory.createInnerClass(aInnerName, aOuterName, aInnerSimpleName,
            aModifiers));
    }

    @Override
    public void startField(final int aModifiers, final String aName, final String aDescriptor) {
        startField(aModifiers, aName, aDescriptor, null, null);
    }

    @Override
    public void startField(final int aModifiers, final String aName, final Class<?> aType) {
        startField(aModifiers, aName, Type.getDescriptor(aType));
    }

    @Override
    public void startField(final int aModifiers, final String aName, final String aDescriptor, final String aSignature,
        final Object aConstantValue)
    {
        dispatchBytecodeEvent(bytecodeEventFactory.createFieldStart(aModifiers, aName, aDescriptor, aSignature,
            aConstantValue));
    }

    @Override
    public void endField() {
        dispatchBytecodeEvent(bytecodeEventFactory.createFieldEnd());
    }

    @Override
    public void startMethod(final int aModifiers, final String aName, final String aDescriptor) {
        startMethod(aModifiers, aName, aDescriptor, null, null);
    }

    @Override
    public void startMethod(final int aModifiers, final String aName, final String aReturnType,
        final String... aParameterTypes)
    {
        startMethod(aModifiers, aName, buildDescriptor(aReturnType, aParameterTypes));
    }

    @Override
    public void startMethod(final int aModifiers, final String aName, final Class<?> aReturnType,
        final Class<?>... aParameterTypes)
    {
        startMethod(aModifiers, aName, Type.getDescriptor(aReturnType), getDescriptors(aParameterTypes));
    }

    @Override
    public void startMethod(final int aModifiers, final String aName, final String aDescriptor,
        final String aSignature, final String[] aExceptionNames)
    {
        dispatchBytecodeEvent(bytecodeEventFactory.createMethodStart(aModifiers, aName, aDescriptor, aSignature,
            aExceptionNames));
    }

    @Override
    public void startCode() {
        dispatchBytecodeEvent(bytecodeEventFactory.createCodeStart());
    }

    @Override
    public void doNothing() {
        dispatchBytecodeEvent(bytecodeEventFactory.createInstruction(Opcodes.NOP));
    }

    @Override
    public void instanceOf(final String aTypeName) {
        dispatchBytecodeEvent(bytecodeEventFactory.createTypeInstruction(Opcodes.INSTANCEOF, aTypeName));
    }

    @Override
    public void instanceOf(final Type aType) {
        instanceOf(aType.getClassName());
    }

    @Override
    public void instanceOf(final Class<?> aType) {
        instanceOf(Type.getType(aType));
    }

    @Override
    public void invokeStatic(final String aOwnerName, final String aMethodName, final String aMethodDescriptor) {
        dispatchBytecodeEvent(bytecodeEventFactory.createMethodInstruction(Opcodes.INVOKESTATIC, aOwnerName,
            aMethodName, aMethodDescriptor));
    }

    @Override
    public void invokeStatic(final String aOwnerName, final String aMethodName, final String aReturnType,
        final String... aParameterTypes)
    {
        invokeStatic(aOwnerName, aMethodName, buildDescriptor(aReturnType, aParameterTypes));
    }

    @Override
    public void invokeStatic(final String aOwnerName, final String aMethodName, final Class<?> aReturnType,
        final Class<?>... aParameterTypes)
    {
        invokeStatic(aOwnerName, aMethodName, Type.getDescriptor(aReturnType), getDescriptors(aParameterTypes));
    }

    @Override
    public void invokeStatic(final Class<?> aOwnerClass, final String aMethodName, final Class<?> aReturnType,
        final Class<?>... aParameterTypes)
    {
        invokeStatic(Type.getInternalName(aOwnerClass), aMethodName, Type.getDescriptor(aReturnType),
            getDescriptors(aParameterTypes));
    }

    @Override
    public void invokeConstructor(final String aOwnerName, final String aConstructorDescriptor) {
        dispatchBytecodeEvent(bytecodeEventFactory.createMethodInstruction(Opcodes.INVOKESPECIAL, aOwnerName, "<init>",
            aConstructorDescriptor));
    }

    @Override
    public void invokeConstructor(final String aOwnerName, final String... aParameterTypes) {
        invokeConstructor(aOwnerName, buildDescriptor("V", aParameterTypes));
    }

    @Override
    public void invokeConstructor(final String aOwnerName, final Class<?>... aParameterTypes) {
        invokeConstructor(aOwnerName, buildDescriptor(void.class, aParameterTypes));
    }

    @Override
    public void invokeConstructor(final Class<?> aOwnerClass, final Class<?>... aParameterTypes) {
        invokeConstructor(Type.getInternalName(aOwnerClass), getDescriptors(aParameterTypes));
    }

    @Override
    public void invokeSpecial(final String aOwnerName, final String aMethodName, final String aMethodDescriptor) {
        dispatchBytecodeEvent(bytecodeEventFactory.createMethodInstruction(Opcodes.INVOKESPECIAL, aOwnerName,
            aMethodName, aMethodDescriptor));
    }

    @Override
    public void invokeSpecial(final String aOwnerName, final String aMethodName, final String aReturnType,
        final String... aParameterTypes)
    {
        invokeSpecial(aOwnerName, aMethodName, buildDescriptor(aReturnType, aParameterTypes));
    }

    @Override
    public void invokeSpecial(final String aOwnerName, final String aMethodName, final Class<?> aReturnType,
        final Class<?>... aParameterTypes)
    {
        invokeSpecial(aOwnerName, aMethodName, Type.getDescriptor(aReturnType), getDescriptors(aParameterTypes));
    }

    @Override
    public void invokeSpecial(final Class<?> aOwnerClass, final String aMethodName, final Class<?> aReturnType,
        final Class<?>... aParameterTypes)
    {
        invokeSpecial(Type.getInternalName(aOwnerClass), aMethodName, Type.getDescriptor(aReturnType),
            getDescriptors(aParameterTypes));
    }

    @Override
    public void invokeInterface(final String aOwnerName, final String aMethodName, final String aMethodDescriptor) {
        dispatchBytecodeEvent(bytecodeEventFactory.createMethodInstruction(Opcodes.INVOKEINTERFACE, aOwnerName,
            aMethodName, aMethodDescriptor));
    }

    @Override
    public void invokeInterface(final String aOwnerName, final String aMethodName, final String aReturnType,
        final String... aParameterTypes)
    {
        invokeInterface(aOwnerName, aMethodName, buildDescriptor(aReturnType, aParameterTypes));
    }

    @Override
    public void invokeInterface(final String aOwnerName, final String aMethodName, final Class<?> aReturnType,
        final Class<?>... aParameterTypes)
    {
        invokeInterface(aOwnerName, aMethodName, Type.getDescriptor(aReturnType), getDescriptors(aParameterTypes));
    }

    @Override
    public void invokeInterface(final Class<?> aOwnerClass, final String aMethodName, final Class<?> aReturnType,
        final Class<?>... aParameterTypes)
    {
        invokeInterface(Type.getInternalName(aOwnerClass), aMethodName, Type.getDescriptor(aReturnType),
            getDescriptors(aParameterTypes));
    }

    @Override
    public void invokeVirtual(final String aOwnerName, final String aMethodName, final String aMethodDescriptor) {
        dispatchBytecodeEvent(bytecodeEventFactory.createMethodInstruction(Opcodes.INVOKEVIRTUAL, aOwnerName,
            aMethodName, aMethodDescriptor));
    }

    @Override
    public void invokeVirtual(final String aOwnerName, final String aMethodName, final String aReturnType,
        final String... aParameterTypes)
    {
        invokeVirtual(aOwnerName, aMethodName, buildDescriptor(aReturnType, aParameterTypes));
    }

    @Override
    public void invokeVirtual(final String aOwnerName, final String aMethodName, final Class<?> aReturnType,
        final Class<?>... aParameterTypes)
    {
        invokeVirtual(aOwnerName, aMethodName, Type.getDescriptor(aReturnType), getDescriptors(aParameterTypes));
    }

    @Override
    public void invokeVirtual(final Class<?> aOwnerClass, final String aMethodName, final Class<?> aReturnType,
        final Class<?>... aParameterTypes)
    {
        invokeVirtual(Type.getInternalName(aOwnerClass), aMethodName, Type.getDescriptor(aReturnType),
            getDescriptors(aParameterTypes));
    }

    private String[] getDescriptors(final Class<?>... aTypes) {
        final String[] descriptors = new String[aTypes.length];
        for (int i = 0; i < aTypes.length; i++) {
            descriptors[i] = Type.getDescriptor(aTypes[i]);
        }
        return descriptors;
    }

    private String buildDescriptor(final Class<?> aReturnType, final Class<?>... aParameterTypes) {
        return buildDescriptor(Type.getDescriptor(aReturnType), getDescriptors(aParameterTypes));
    }

    private String buildDescriptor(final String aReturnType, final String... aParameterTypes) {
        final StringBuilder stringBuilder = new StringBuilder('(');
        for (String parameterType : aParameterTypes) {
            stringBuilder.append(parameterType);
        }
        stringBuilder.append(')');
        stringBuilder.append(aReturnType);
        return stringBuilder.toString();
    }

    @Override
    public void newReference(final String aTypeName) {
        dispatchBytecodeEvent(bytecodeEventFactory.createTypeInstruction(Opcodes.NEW, aTypeName));
    }

    @Override
    public void newReference(final Class<?> aType) {
        newReference(Type.getInternalName(aType));
    }

    @Override
    public void newReferenceArray(final String aComponentTypeName) {
        dispatchBytecodeEvent(bytecodeEventFactory.createTypeInstruction(Opcodes.ANEWARRAY, aComponentTypeName));
    }

    @Override
    public void newReferenceArray(final Class<?> aComponentTypeName) {
        newReferenceArray(Type.getInternalName(aComponentTypeName));
    }

    @Override
    public void duplicateReference() {
        dispatchBytecodeEvent(bytecodeEventFactory.createInstruction(Opcodes.DUP));
    }

    @Override
    public void pushNull() {
        dispatchBytecodeEvent(bytecodeEventFactory.createInstruction(Opcodes.ACONST_NULL));
    }

    @Override
    public void pushConstant(final Object aConstant) {
        if (aConstant instanceof Type) {
            final Type constantType = (Type) aConstant;
            switch (constantType.getSort()) {
            case Type.VOID:
                dispatchBytecodeEvent(bytecodeEventFactory.createFieldInstruction(Opcodes.GETSTATIC, "java/lang/Void",
                    "TYPE", "Ljava/lang/Class;"));
                break;
            case Type.BOOLEAN:
                dispatchBytecodeEvent(bytecodeEventFactory.createFieldInstruction(Opcodes.GETSTATIC,
                    "java/lang/Boolean", "TYPE", "Ljava/lang/Class;"));
                break;
            case Type.CHAR:
                dispatchBytecodeEvent(bytecodeEventFactory.createFieldInstruction(Opcodes.GETSTATIC,
                    "java/lang/Character", "TYPE", "Ljava/lang/Class;"));
                break;
            case Type.BYTE:
                dispatchBytecodeEvent(bytecodeEventFactory.createFieldInstruction(Opcodes.GETSTATIC, "java/lang/Byte",
                    "TYPE", "Ljava/lang/Class;"));
                break;
            case Type.SHORT:
                dispatchBytecodeEvent(bytecodeEventFactory.createFieldInstruction(Opcodes.GETSTATIC, "java/lang/Short",
                    "TYPE", "Ljava/lang/Class;"));
                break;
            case Type.INT:
                dispatchBytecodeEvent(bytecodeEventFactory.createFieldInstruction(Opcodes.GETSTATIC,
                    "java/lang/Integer", "TYPE", "Ljava/lang/Class;"));
                break;
            case Type.FLOAT:
                dispatchBytecodeEvent(bytecodeEventFactory.createFieldInstruction(Opcodes.GETSTATIC, "java/lang/Float",
                    "TYPE", "Ljava/lang/Class;"));
                break;
            case Type.LONG:
                dispatchBytecodeEvent(bytecodeEventFactory.createFieldInstruction(Opcodes.GETSTATIC, "java/lang/Long",
                    "TYPE", "Ljava/lang/Class;"));
                break;
            case Type.DOUBLE:
                dispatchBytecodeEvent(bytecodeEventFactory.createFieldInstruction(Opcodes.GETSTATIC,
                    "java/lang/Double", "TYPE", "Ljava/lang/Class;"));
                break;
            // ARRAY ?
            default:
                dispatchBytecodeEvent(bytecodeEventFactory.createLoadConstantInstruction(aConstant));
                break;
            }
        } else {
            dispatchBytecodeEvent(bytecodeEventFactory.createLoadConstantInstruction(aConstant));
        }
    }

    @Override
    public void pushConstant(final Class<?> aType) {
        pushConstant(Type.getType(aType));
    }

    @Override
    public void pushBoolean(final boolean aBoolean) {
        if (aBoolean) {
            pushInteger(1);
        } else {
            pushInteger(0);
        }
    }

    @Override
    public void pushInteger(final int aInteger) {
        switch (aInteger) {
        case -1:
            dispatchBytecodeEvent(bytecodeEventFactory.createInstruction(Opcodes.ICONST_M1));
            return;
        case 0:
            dispatchBytecodeEvent(bytecodeEventFactory.createInstruction(Opcodes.ICONST_0));
            return;
        case 1:
            dispatchBytecodeEvent(bytecodeEventFactory.createInstruction(Opcodes.ICONST_1));
            return;
        case 2:
            dispatchBytecodeEvent(bytecodeEventFactory.createInstruction(Opcodes.ICONST_2));
            return;
        case 3:
            dispatchBytecodeEvent(bytecodeEventFactory.createInstruction(Opcodes.ICONST_3));
            return;
        case 4:
            dispatchBytecodeEvent(bytecodeEventFactory.createInstruction(Opcodes.ICONST_4));
            return;
        case 5:
            dispatchBytecodeEvent(bytecodeEventFactory.createInstruction(Opcodes.ICONST_5));
            return;
        default:
            break;
        }

        if (Byte.MIN_VALUE <= aInteger && aInteger <= Byte.MAX_VALUE) {
            dispatchBytecodeEvent(bytecodeEventFactory.createIntInstruction(Opcodes.BIPUSH, aInteger));
            return;
        }

        if (Short.MIN_VALUE <= aInteger && aInteger <= Short.MAX_VALUE) {
            dispatchBytecodeEvent(bytecodeEventFactory.createIntInstruction(Opcodes.SIPUSH, aInteger));
            return;
        }

        dispatchBytecodeEvent(bytecodeEventFactory.createIntInstruction(Opcodes.LDC, aInteger));
    }

    @Override
    public void loadVariable(final int aLocalVariableIndex, final Type aLocalVariableType) {
        switch (aLocalVariableType.getSort()) {
        case Type.VOID:
            throw new BytecodeException("Cannot load local variable at index " + aLocalVariableIndex + " of type "
                + aLocalVariableType);
        case Type.BOOLEAN: // falls through
        case Type.CHAR: // falls through
        case Type.BYTE: // falls through
        case Type.SHORT: // falls through
        case Type.INT:
            dispatchBytecodeEvent(bytecodeEventFactory.createVariableInstruction(Opcodes.ILOAD, aLocalVariableIndex));
            break;
        case Type.FLOAT:
            dispatchBytecodeEvent(bytecodeEventFactory.createVariableInstruction(Opcodes.FLOAD, aLocalVariableIndex));
            break;
        case Type.LONG:
            dispatchBytecodeEvent(bytecodeEventFactory.createVariableInstruction(Opcodes.LLOAD, aLocalVariableIndex));
            break;
        case Type.DOUBLE:
            dispatchBytecodeEvent(bytecodeEventFactory.createVariableInstruction(Opcodes.DLOAD, aLocalVariableIndex));
            break;
        default:
            dispatchBytecodeEvent(bytecodeEventFactory.createVariableInstruction(Opcodes.ALOAD, aLocalVariableIndex));
            break;
        }
    }

    @Override
    public void loadInteger(final int aLocalVariableIndex) {
        dispatchBytecodeEvent(bytecodeEventFactory.createVariableInstruction(Opcodes.ILOAD, aLocalVariableIndex));
    }

    @Override
    public void loadReference(final int aLocalVariableIndex) {
        dispatchBytecodeEvent(bytecodeEventFactory.createVariableInstruction(Opcodes.ALOAD, aLocalVariableIndex));
    }

    @Override
    public void loadReferenceArray(final int aArrayIndex) {
        pushInteger(aArrayIndex);
        dispatchBytecodeEvent(bytecodeEventFactory.createInstruction(Opcodes.AALOAD));
    }

    @Override
    public void loadStatic(final String aOwnerName, final String aFieldName, final String aFieldDescriptor) {
        dispatchBytecodeEvent(bytecodeEventFactory.createFieldInstruction(Opcodes.GETSTATIC, aOwnerName, aFieldName,
            aFieldDescriptor));
    }

    @Override
    public void loadStatic(final String aOwnerName, final String aFieldName, final Class<?> aFieldType) {
        loadStatic(aOwnerName, aFieldName, Type.getDescriptor(aFieldType));
    }

    @Override
    public void loadStatic(final Class<?> aOwnerClass, final String aFieldName, final Class<?> aFieldType) {
        loadStatic(Type.getInternalName(aOwnerClass), aFieldName, Type.getDescriptor(aFieldType));
    }

    @Override
    public void loadField(final String aOwnerName, final String aFieldName, final String aFieldDescriptor) {
        dispatchBytecodeEvent(bytecodeEventFactory.createFieldInstruction(Opcodes.GETFIELD, aOwnerName, aFieldName,
            aFieldDescriptor));
    }

    @Override
    public void loadField(final String aOwnerName, final String aFieldName, final Class<?> aFieldType) {
        loadField(aOwnerName, aFieldName, Type.getDescriptor(aFieldType));
    }

    @Override
    public void loadField(final Class<?> aOwnerClass, final String aFieldName, final Class<?> aFieldType) {
        loadField(Type.getInternalName(aOwnerClass), aFieldName, Type.getDescriptor(aFieldType));
    }

    @Override
    public void storeInteger(final int aLocalVariableIndex) {
        dispatchBytecodeEvent(bytecodeEventFactory.createVariableInstruction(Opcodes.ISTORE, aLocalVariableIndex));
    }

    @Override
    public void storeReference(final int aLocalVariableIndex) {
        dispatchBytecodeEvent(bytecodeEventFactory.createVariableInstruction(Opcodes.ASTORE, aLocalVariableIndex));
    }

    @Override
    public void storeReferenceArray() {
        dispatchBytecodeEvent(bytecodeEventFactory.createInstruction(Opcodes.AASTORE));
    }

    @Override
    public void storeField(final String aOwnerName, final String aFieldName, final String aFieldDescriptor) {
        dispatchBytecodeEvent(bytecodeEventFactory.createFieldInstruction(Opcodes.PUTFIELD, aOwnerName, aFieldName,
            aFieldDescriptor));
    }

    @Override
    public void storeField(final String aOwnerName, final String aFieldName, final Class<?> aFieldType) {
        storeField(aOwnerName, aFieldName, Type.getDescriptor(aFieldType));
    }

    @Override
    public void storeField(final Class<?> aOwnerClass, final String aFieldName, final Class<?> aFieldType) {
        storeField(Type.getInternalName(aOwnerClass), aFieldName, Type.getDescriptor(aFieldType));
    }

    @Override
    public void popReference() {
        dispatchBytecodeEvent(bytecodeEventFactory.createInstruction(Opcodes.POP));
    }

    @Override
    public void castChecked(final String aTypeName) {
        dispatchBytecodeEvent(bytecodeEventFactory.createTypeInstruction(Opcodes.CHECKCAST, aTypeName));
    }

    @Override
    public void castChecked(final Type aType) {
        switch (aType.getSort()) {
        case Type.VOID:
            throw new BytecodeException("Return type must not be void");
        case Type.BOOLEAN:
            dispatchBytecodeEvent(bytecodeEventFactory.createTypeInstruction(Opcodes.CHECKCAST, "java/lang/Boolean"));
            dispatchBytecodeEvent(bytecodeEventFactory.createMethodInstruction(Opcodes.INVOKEVIRTUAL,
                "java/lang/Boolean", "booleanValue", "()Z"));
            break;
        case Type.CHAR:
            dispatchBytecodeEvent(bytecodeEventFactory.createTypeInstruction(Opcodes.CHECKCAST, "java/lang/Character"));
            dispatchBytecodeEvent(bytecodeEventFactory.createMethodInstruction(Opcodes.INVOKEVIRTUAL,
                "java/lang/Character", "charValue", "()C"));
            break;
        case Type.BYTE:
            dispatchBytecodeEvent(bytecodeEventFactory.createTypeInstruction(Opcodes.CHECKCAST, "java/lang/Byte"));
            dispatchBytecodeEvent(bytecodeEventFactory.createMethodInstruction(Opcodes.INVOKEVIRTUAL, "java/lang/Byte",
                "byteValue", "()B"));
            break;
        case Type.SHORT:
            dispatchBytecodeEvent(bytecodeEventFactory.createTypeInstruction(Opcodes.CHECKCAST, "java/lang/Short"));
            dispatchBytecodeEvent(bytecodeEventFactory.createMethodInstruction(Opcodes.INVOKEVIRTUAL,
                "java/lang/Short", "shortValue", "()S"));
            break;
        case Type.INT:
            dispatchBytecodeEvent(bytecodeEventFactory.createTypeInstruction(Opcodes.CHECKCAST, "java/lang/Integer"));
            dispatchBytecodeEvent(bytecodeEventFactory.createMethodInstruction(Opcodes.INVOKEVIRTUAL,
                "java/lang/Integer", "intValue", "()I"));
            break;
        case Type.FLOAT:
            dispatchBytecodeEvent(bytecodeEventFactory.createTypeInstruction(Opcodes.CHECKCAST, "java/lang/Float"));
            dispatchBytecodeEvent(bytecodeEventFactory.createMethodInstruction(Opcodes.INVOKEVIRTUAL,
                "java/lang/Float", "floatValue", "()F"));
            break;
        case Type.LONG:
            dispatchBytecodeEvent(bytecodeEventFactory.createTypeInstruction(Opcodes.CHECKCAST, "java/lang/Long"));
            dispatchBytecodeEvent(bytecodeEventFactory.createMethodInstruction(Opcodes.INVOKEVIRTUAL, "java/lang/Long",
                "longValue", "()J"));
            break;
        case Type.DOUBLE:
            dispatchBytecodeEvent(bytecodeEventFactory.createTypeInstruction(Opcodes.CHECKCAST, "java/lang/Double"));
            dispatchBytecodeEvent(bytecodeEventFactory.createMethodInstruction(Opcodes.INVOKEVIRTUAL,
                "java/lang/Double", "doubleValue", "()D"));
            break;
        default:
            dispatchBytecodeEvent(bytecodeEventFactory
                .createTypeInstruction(Opcodes.CHECKCAST, aType.getInternalName()));
            break;
        }
    }

    @Override
    public void castChecked(final Class<?> aType) {
        castChecked(Type.getType(aType));
    }

    @Override
    public void incrementInteger(final int aLocalVariableIndex) {
        dispatchBytecodeEvent(bytecodeEventFactory.createIincInstruction(aLocalVariableIndex, 1));
    }

    @Override
    public void returnVoid() {
        dispatchBytecodeEvent(bytecodeEventFactory.createInstruction(Opcodes.RETURN));
    }

    @Override
    public void returnReference() {
        dispatchBytecodeEvent(bytecodeEventFactory.createInstruction(Opcodes.ARETURN));
    }

    @Override
    public void invokeValueOf(final Type aType) {
        switch (aType.getSort()) {
        case Type.VOID:
            throw new BytecodeException("Cannot auto-box void type");
        case Type.BOOLEAN:
            invokeStatic(Boolean.class, "valueOf", Boolean.class, boolean.class);
            break;
        case Type.CHAR:
            invokeStatic(Character.class, "valueOf", Character.class, char.class);
            break;
        case Type.BYTE:
            invokeStatic(Byte.class, "valueOf", Byte.class, byte.class);
            break;
        case Type.SHORT:
            invokeStatic(Short.class, "valueOf", Short.class, short.class);
            break;
        case Type.INT:
            invokeStatic(Integer.class, "valueOf", Integer.class, int.class);
            break;
        case Type.FLOAT:
            invokeStatic(Float.class, "valueOf", Float.class, float.class);
            break;
        case Type.LONG:
            invokeStatic(Long.class, "valueOf", Long.class, long.class);
            break;
        case Type.DOUBLE:
            invokeStatic(Double.class, "valueOf", Double.class, double.class);
            break;
        default:
            // do nothing here; it's an array or an object
            break;
        }
    }

    @Override
    public void returnVariable(final Type aReturnType) {
        switch (aReturnType.getSort()) {
        case Type.VOID:
            returnVoid();
            break;
        case Type.BOOLEAN: // falls through
        case Type.CHAR: // falls through
        case Type.BYTE: // falls through
        case Type.SHORT: // falls through
        case Type.INT:
            bytecodeEventFactory.createInstruction(Opcodes.IRETURN);
            break;
        case Type.FLOAT:
            bytecodeEventFactory.createInstruction(Opcodes.FRETURN);
            break;
        case Type.LONG:
            bytecodeEventFactory.createInstruction(Opcodes.LRETURN);
            break;
        case Type.DOUBLE:
            bytecodeEventFactory.createInstruction(Opcodes.DRETURN);
            break;
        default:
            returnReference();
            break;
        }
    }

    @Override
    public void returnReference(final Type aReturnType) {
        invokeValueOf(aReturnType);
        returnReference();
    }

    @Override
    public void throwReference() {
        dispatchBytecodeEvent(bytecodeEventFactory.createInstruction(Opcodes.ATHROW));
    }

    @Override
    public void markLabel(final Label aLabel) {
        dispatchBytecodeEvent(bytecodeEventFactory.createLabel(aLabel));
    }

    @Override
    public void markTryCatchBlock(final Label aStartLabel, final Label aEndLabel, final Label aHandlerLabel,
        final String aExceptionTypeName)
    {
        dispatchBytecodeEvent(bytecodeEventFactory.createTryCatchBlock(aStartLabel, aEndLabel, aHandlerLabel,
            aExceptionTypeName));
    }

    @Override
    public void gotoLabel(final Label aLabel) {
        dispatchBytecodeEvent(bytecodeEventFactory.createJumpInstruction(Opcodes.GOTO, aLabel));
    }

    @Override
    public void jumpNull(final Label aLabel) {
        dispatchBytecodeEvent(bytecodeEventFactory.createJumpInstruction(Opcodes.IFNULL, aLabel));
    }

    @Override
    public void jumpNotNull(final Label aLabel) {
        dispatchBytecodeEvent(bytecodeEventFactory.createJumpInstruction(Opcodes.IFNONNULL, aLabel));
    }

    @Override
    public void jumpEquals(final Label aLabel) {
        dispatchBytecodeEvent(bytecodeEventFactory.createJumpInstruction(Opcodes.IFEQ, aLabel));
    }

    @Override
    public void jumpNotEquals(final Label aLabel) {
        dispatchBytecodeEvent(bytecodeEventFactory.createJumpInstruction(Opcodes.IFNE, aLabel));
    }

    @Override
    public void jumpIntegerLessThan(final Label aLabel) {
        dispatchBytecodeEvent(bytecodeEventFactory.createJumpInstruction(Opcodes.IF_ICMPLE, aLabel));
    }

    @Override
    public void markFrameSame() {
        dispatchBytecodeEvent(bytecodeEventFactory.createFrame(Opcodes.F_SAME, 0, null, 0, null));
    }

    @Override
    public void markFrameAppend(final int aLocalVariables, final Object[] aLocalVariableTypes,
        final int aStackElements, final Object[] aStackElementTypes)
    {
        dispatchBytecodeEvent(bytecodeEventFactory.createFrame(Opcodes.F_APPEND, aLocalVariables, aLocalVariableTypes,
            aStackElements, aStackElementTypes));
    }

    @Override
    public void markFrameSame1(final int aLocalVariables, final Object[] aLocalVariableTypes, final int aStackElements,
        final Object[] aStackElementTypes)
    {
        dispatchBytecodeEvent(bytecodeEventFactory.createFrame(Opcodes.F_SAME1, aLocalVariables, aLocalVariableTypes,
            aStackElements, aStackElementTypes));
    }

    @Override
    public void markFrameChop(int aLocalVariables, Object[] aLocalVariableTypes, int aStackElements,
        Object[] aStackElementTypes)
    {
        dispatchBytecodeEvent(bytecodeEventFactory.createFrame(Opcodes.F_CHOP, aLocalVariables, aLocalVariableTypes,
            aStackElements, aStackElementTypes));
    }

    @Override
    public void markFrameFull(int aLocalVariables, Object[] aLocalVariableTypes, int aStackElements,
        Object[] aStackElementTypes)
    {
        dispatchBytecodeEvent(bytecodeEventFactory.createFrame(Opcodes.F_FULL, aLocalVariables, aLocalVariableTypes,
            aStackElements, aStackElementTypes));
    }

    @Override
    public void endCode(final int aMaximumStackSize, final int aMaximumLocalVariables) {
        dispatchBytecodeEvent(bytecodeEventFactory.createCodeEnd(aMaximumStackSize, aMaximumLocalVariables));
    }

    @Override
    public void endMethod() {
        dispatchBytecodeEvent(bytecodeEventFactory.createMethodEnd());
    }

    @Override
    public void endClass() {
        dispatchBytecodeEvent(bytecodeEventFactory.createClassEnd());
    }

    @Override
    public void dispatchBytecodeEvent(final BytecodeEvent aBytecodeEvent) {
        super.dispatchBytecodeEvent(aBytecodeEvent);
    }

    @Override
    public void dispatchBytecodeEvents(final Collection<BytecodeEvent> aBytecodeEvents) {
        for (BytecodeEvent bytecodeEvent : aBytecodeEvents) {
            dispatchBytecodeEvent(bytecodeEvent);
        }
    }

    @Override
    public void startSourcing() {
    }

    @Override
    public void postponeSourcing() {
    }

    @Override
    public void postponeSourcing(final ResumptionCondition aResumptionCondition) {
    }

    @Override
    public void cancelSourcing() {
    }

    @Override
    public boolean isStarted() {
        return false;
    }

    @Override
    public boolean isPostponed() {
        return false;
    }

    @Override
    public boolean isCancelled() {
        return false;
    }

    @Override
    public ResumptionCondition getResumptionCondition() {
        return null;
    }
}
